#
# TODO List:
# - Manage - Destroy: Add confirmation
# - Has Many
# - Has and Belongs to Many
# - Validators

class FlexScaffoldGenerator < Rails::Generator::NamedBase
  attr_accessor :flex_src_dir
  
  def manifest
    #This is the directory within the Flex project for your source code
    # Flex Builder 3.0 suggests the use of src
    @flex_src_dir = 'src'
    
    record do |m|
      
      # Create the integrated templates
      create_services_template      
      create_weborb_config_template
      create_controller_template
      create_model_locator_template
      create_application_template
      
      # Flex Code
      m.template "vo_template.as",              File.join('flex', @flex_src_dir, class_path, file_name,'vo', "#{actions.first.camelcase}VO.as")
      m.template "delegate_template.as",        File.join('flex', @flex_src_dir, class_path, file_name,'business', "#{actions.first.camelcase}Delegate.as")      
      m.template "base_command_template.as",    File.join('flex', @flex_src_dir, class_path, file_name,'command', "#{actions.first.camelcase}BaseCommand.as")      
      m.template "find_all_command_template.as",File.join('flex', @flex_src_dir, class_path, file_name,'command', "#{actions.first.camelcase}FindAllCommand.as")      
      m.template "find_by_id_command_template.as",File.join('flex', @flex_src_dir, class_path, file_name,'command', "#{actions.first.camelcase}FindByIdCommand.as")      
      m.template "save_command_template.as",    File.join('flex', @flex_src_dir, class_path, file_name,'command', "#{actions.first.camelcase}SaveCommand.as")      
      m.template "destroy_command_template.as", File.join('flex', @flex_src_dir, class_path, file_name,'command', "#{actions.first.camelcase}DestroyCommand.as")      
      m.template "manager_template.as",         File.join('flex', @flex_src_dir, class_path, file_name,'ui', "#{actions.first.camelcase}Manager.as")      
      m.template "Listing-template.mxml",       File.join('flex', @flex_src_dir, 'components', "#{actions.first.camelcase}Listing.mxml")
      m.template "DataGrid-template.mxml",      File.join('flex', @flex_src_dir, 'components', "#{actions.first.camelcase}DataGrid.mxml")
      m.template "Edit-Window-template.mxml",   File.join('flex', @flex_src_dir, 'components', "#{actions.first.camelcase}EditWindow.mxml")

      # Code updated by the generator
      m.template "weborb-config_tmp.xml",  File.join('config', "weborb-config.xml")
      m.template "main_tmp.mxml",          File.join('flex', @flex_src_dir, 'main.mxml')
      m.template "Services_tmp.mxml",      File.join('flex', @flex_src_dir, class_path, file_name, 'business', "Services.mxml")
      m.template "Controller_tmp.as",      File.join('flex', @flex_src_dir, class_path, file_name,  'control', "#{file_name.camelcase}Controller.as")
      m.template "ModelLocator_tmp.as",    File.join('flex', @flex_src_dir, class_path, file_name, 'model', "#{file_name.camelcase}ModelLocator.as")
      
      # Ruby Code
      m.template "service_template.rb",    File.join('app', 'services', "#{actions.first.camelcase}Service.rb")
        
    end
  end
  
  def create_services_template
    
    # Set the file names
    original_file_path = File.join('flex', @flex_src_dir, class_path, file_name, 'business', "Services.mxml")
    template_file_path = File.join('lib', 'generators', 'flex_scaffold', 'templates', 'Services_tmp.mxml')

    # Build the unique identifier to prevent duplication
  	unique_text = 'id="' +  actions.first.camelcase + 'Service"'
    
  	# Build the file addition
  	file_addition = "\t<mx:RemoteObject "
  	file_addition += unique_text
  	file_addition += ' destination="'
  	file_addition += actions.first.camelcase 
  	file_addition += 'Service" endpoint="/weborb" showBusyCursor="true" />'

    # Where will the addition go?
  	file_marker = "\n</cairngorm:ServiceLocator>"

    # Create the template
    create_integration_template(original_file_path, template_file_path, unique_text, file_addition, file_marker)
  end

  def create_weborb_config_template
    
    # Set the file names
    original_file_path = File.join('config', "weborb-config.xml")
    template_file_path = File.join('lib', 'generators', 'flex_scaffold', 'templates', 'weborb-config_tmp.xml')

    # Build the unique identifier to prevent duplication
  	unique_text = "#{class_name.downcase.gsub('::','.')}.vo.#{actions.first.camelcase}VO"
    
  	# Build the file addition
  	file_addition = "\t\t<classMapping>\n"
  	file_addition += "\t\t\t<clientClass>#{unique_text}</clientClass>\n"
  	file_addition += "\t\t\t<serverClass>#{actions.first.camelcase}</serverClass>\n"
  	file_addition += "\t\t\t<source>#{actions.first}</source>\n"
  	file_addition += "\t\t</classMapping>"

    # Where will the addition go?
  	file_marker = "</classMappings>"

    # Create the template
    create_integration_template(original_file_path, template_file_path, unique_text, file_addition, file_marker)
  end  
  
  def create_controller_template
    
    # Set the file names
    original_file_path = File.join('flex', @flex_src_dir, class_path, file_name,  'control', "#{file_name.camelcase}Controller.as")
    template_file_path = File.join('lib', 'generators', 'flex_scaffold', 'templates', 'Controller_tmp.as')

    # Build the unique identifier to prevent duplication
  	unique_text = " #{actions.first.camelcase}FindAllCommand"
    
  	# Build the file addition
  	file_addition  = "\n\t\t\t/* #{actions.first.camelcase} */\n"
  	
  	file_addition += "\t\t\taddCommand( getFindAllCommandName(\"#{actions.first.camelcase}\"), #{actions.first.camelcase}FindAllCommand );\n"
  	file_addition += "\t\t\taddCommand( getFindByIdCommandName(\"#{actions.first.camelcase}\"), #{actions.first.camelcase}FindByIdCommand );\n"
  	file_addition += "\t\t\taddCommand( getSaveCommandName(\"#{actions.first.camelcase}\"), #{actions.first.camelcase}SaveCommand );\n"
  	file_addition += "\t\t\taddCommand( getDestroyCommandName(\"#{actions.first.camelcase}\"), #{actions.first.camelcase}DestroyCommand );\n"

    # Where will the addition go?
  	file_marker = "\t\tpublic function initializeCommands() : void {"
  
    # Create the template
    create_integration_template(original_file_path, template_file_path, unique_text, file_addition, file_marker, 'after_marker')
  end  
  
  def create_model_locator_template
    #Create something like blogPost out of blog_post
    obj_name = actions.first.camelcase
    obj_name = obj_name[0,1].downcase + obj_name[1,obj_name.length]
    
    # Set the file names
    original_file_path = File.join('flex', @flex_src_dir, class_path, file_name, 'model', "#{file_name.camelcase}ModelLocator.as")
    template_file_path = File.join('lib', 'generators', 'flex_scaffold', 'templates', 'ModelLocator_tmp.as')

    # Build the unique identifier to prevent duplication
  	unique_text = "var #{obj_name.pluralize} : ArrayCollection"
  	
  	# Build the file addition
  	file_addition =  "\t\tpublic #{unique_text};"  	

    # Where will the addition go?
  	file_marker = "/* Collections */\r\n"
  
    # Create the template
    create_integration_template(original_file_path, template_file_path, unique_text, file_addition, file_marker, 'after_marker')
  end  

  def create_application_template
    
    # Create the helper
  	flex_scaffold_helper = FlexScaffoldHelper.new("#{actions.first.camelcase}")
    
    # Set the file names
    original_file_path = File.join('flex', @flex_src_dir,'main.mxml')
    template_file_path = File.join('lib', 'generators', 'flex_scaffold', 'templates', 'main_tmp.mxml')

    # Build the unique identifier to prevent duplication
  	unique_text = "<components:#{actions.first.camelcase}Listing"
  	
  	# Build the file addition
  	file_addition =  "\t\t\t#{unique_text} id=\"manage#{actions.first.camelcase.pluralize}\" label=\"#{flex_scaffold_helper.title.pluralize}\" dataProvider=\"{model.#{flex_scaffold_helper.create_obj_name(actions.first.camelcase).pluralize}}\" />"  	

    # Where will the addition go?
  	file_marker = "<mx:TabNavigator id=\"tnMain\" width=\"100%\" height=\"100%\">\r\n"

    # Create the template
    create_integration_template(original_file_path, template_file_path, unique_text, file_addition, file_marker, 'after_marker')
  end  

  def create_integration_template(original_file_path, template_file_path, unique_text, file_addition, file_marker, insert_position = 'before_marker')

  	# Read the original file
  	new_file_data = ''
  	File.open(original_file_path).each { |line|	   
  		new_file_data += line
  	}

  	# Look to see if this unique text already exists and if the marker exists
  	if (!new_file_data.include? unique_text and new_file_data.include? file_marker) 

  		# Find the start of the marker
  		marker_pos = new_file_data.index(file_marker)

      if (insert_position == 'after_marker') 
    		# Drop the addition right after the marker
    		marker_pos += file_marker.length
      end    		

  		# Drop the addition right at the marker
  		new_file_data = new_file_data[0,marker_pos] + file_addition + "\n" + new_file_data[marker_pos, new_file_data.length - marker_pos]
  		
  	end    

  	# Write the new template
  	File.open(template_file_path, "w") { |f| f << new_file_data }	   
  end
end

class FlexScaffoldHelper
  attr_accessor :class_name, :title, :source, :object_columns, :object_content_columns, :obj_name, :belongs_to_class_names, :has_many_associations, :habtm_associations

  def initialize(class_name)
    
  		# Read the columns from the object
  		@class_name = class_name  #BlogPost
  		@title = class_name.titleize #Blog Post
  		@source = class_name.underscore #blog_post
			@target_object = Object.const_get(class_name).new
			@object_columns = Object.const_get(class_name).columns
			@object_content_columns = Object.const_get(class_name).content_columns

      # Create something like blogPost out of BlogPost
			@obj_name = create_obj_name(class_name)
			@belongs_to_class_names = Hash.new
			@has_many_associations = Array.new
			@habtm_associations = Array.new 
			

			# Extract belongs_to relationships
      Object.const_get(class_name).reflect_on_all_associations(:belongs_to).each do |assoc|
        # Build a hash that points primary_key_name to the associated class' name
        @belongs_to_class_names[assoc.primary_key_name] = assoc
			end

      # We'll cheat and drop has_one into the belongs-to category
      Object.const_get(class_name).reflect_on_all_associations(:has_one).each do |assoc|
        # Build a hash that points primary_key_name to the associated class' name
        @belongs_to_class_names[assoc.primary_key_name] = assoc
			end

			# Extract has_many relationships
      Object.const_get(class_name).reflect_on_all_associations(:has_many).each do |assoc|
        # Build an array of the associations
        @has_many_associations << assoc
			end
      
			# Extract has_and_belongs_to_many relationships
      Object.const_get(class_name).reflect_on_all_associations(:has_and_belongs_to_many).each do |assoc|
        # Build an array of the associations
        @habtm_associations << assoc
			end
      
  end
  
  def create_obj_name (class_name)
    return class_name[0,1].downcase + class_name[1,class_name.length]
  end
  
  def get_scaffold_label_field(class_name)
    scaffold_label_field = 'name' #The ol' fall-back
    
    target_class = Object.const_get(class_name)
    target_object = target_class.new

    # Look for a scaffold_label_field (default is the first content column)
    if !target_class.content_columns.nil?
  	  scaffold_label_field = target_class.content_columns[0].name
    end
    
    if target_object.respond_to?('scaffold_label_field')
    	scaffold_label_field = target_object.scaffold_label_field
    end

    return scaffold_label_field

  end
  
  # Use the columns hash to find the actual column for the scaffold_label_field
  def get_scaffold_label_column(class_name)
    target_class = Object.const_get(class_name)
    
    label_field = get_scaffold_label_field(class_name)
        
    if target_class.columns_hash.has_key? label_field
      return target_class.columns_hash[label_field]
    else
      return nil
    end

  end  
  
  def get_flex_type (object_column)

			  db_column_type = object_column.type.to_s

				case db_column_type  
				  
				  when 'text', 'char', 'string', 'time'
				    				flex_type = 'String'
				  when 'integer'
				    				flex_type = 'int'
				  when 'datetime', 'timestamp', 'date'  
				    				flex_type = 'Date'
				  when 'float', 'decimal'
				    				flex_type = 'Number'
				  when 'binary', 'boolean'
				            flex_type = 'Boolean'
				  else  
				    				flex_type = 'String'
				end
				
				return flex_type
  end


  # Based on the Active Record column type, build MXML code to get input from the user
  def get_form_item_mxml (object_column)
    
			  db_column_type = object_column.type.to_s
        
				case db_column_type  				   
			  when 'integer'
			        #This may be a simple integer (age in years) or a foreign key to a belongs_to relationship
			        if @belongs_to_class_names.has_key? object_column.name
			            belongs_to_assoc = @belongs_to_class_names[object_column.name]
			            
	              mxml_code =
                "\t\t<mx:FormItem label=\"#{object_column.human_name}:\" width=\"100%\">\n"+
                "\t\t\t<genuflex:GenuflexAutoComplete id=\"cmb#{object_column.name.camelcase}\" primaryKeyField=\"id\" foreignKeyField=\"#{object_column.name}\" dataProvider=\"{model.#{create_obj_name(belongs_to_assoc.class_name).pluralize}}\"  selectedItemSource=\"{current#{class_name}}\"\n"+
                "\t\t\tprompt=\"-- Select #{belongs_to_assoc.class_name.titleize} --\" toolTip=\"Double-click to edit #{belongs_to_assoc.class_name.titleize}\"\n"+
                "\t\t\tdoubleClick=\"{#{belongs_to_assoc.class_name}Manager.getInstance().handleEdit(parentApplication, cmb#{object_column.name.camelcase}.selectedItem, false)}\" doubleClickEnabled=\"{cmb#{object_column.name.camelcase}.selectedItem != null}\"/>\n"+
                "\t\t</mx:FormItem>"			              
		          end
				  when 'text'  
	            mxml_code = 
              "\t\t<mx:FormItem label=\"#{object_column.human_name}:\" width=\"100%\">\n"+
              "\t\t\t<mx:TextArea id=\"txt#{object_column.name.camelcase}\" text=\"{current#{class_name}.#{object_column.name}}\" width=\"100%\" height=\"100\"/>\n"+
              "\t\t</mx:FormItem>"
				  when 'decimal', 'float'
            mxml_code = 
              "\t\t<mx:FormItem label=\"#{object_column.human_name}:\" width=\"100%\">\n"+
              "\t\t\t<mx:TextInput id=\"txt#{object_column.name.camelcase}\" text=\"{GenuflexDataFormatter.formatNumber(current#{class_name}.#{object_column.name})}\"/>\n"+
              "\t\t</mx:FormItem>"
				  
				  when 'datetime', 'timestamp', 'date'  
            mxml_code = 
            "\t\t<mx:FormItem label=\"#{object_column.human_name}:\" width=\"100%\">\n"+
            "\t\t\t<mx:DateField id=\"df#{object_column.name.camelcase}\" selectedDate=\"{current#{class_name}.#{object_column.name}}\" editable=\"true\"/>\n"+
            "\t\t</mx:FormItem>"
         
         
          when 'binary', 'boolean'
                            			
            mxml_code = 
            "\t\t<mx:FormItem label=\"#{object_column.human_name}:\" width=\"100%\">\n"+
            "\t\t\t<mx:CheckBox id=\"cb#{object_column.name.camelcase}\" selected=\"{current#{class_name}.#{object_column.name}}\" />\n"+          
            "\t\t</mx:FormItem>"            
				  
				end
				
				if mxml_code.nil?
          mxml_code = 
            "\t\t<mx:FormItem label=\"#{object_column.human_name}:\" width=\"100%\">\n"+
            "\t\t\t<mx:TextInput id=\"txt#{object_column.name.camelcase}\"  text=\"{current#{class_name}.#{object_column.name}}\"/>\n"+
            "\t\t</mx:FormItem>"
			  end
				
				return mxml_code
  end

    # Based on the Active Record column type, build MXML code to get input from the user
    def get_data_grid_column_mxml (object_column, object_column_class_name)

  			  db_column_type = object_column.type.to_s
          
  				case db_column_type  				   
  			  when 'integer'
  			        #This may be a simple integer (age in years) or a foreign key to a belongs_to relationship
  			        if @belongs_to_class_names.has_key? object_column.name
  			            belongs_to_assoc = @belongs_to_class_names[object_column.name]

  	              mxml_code = "\t\t\t\t<mx:DataGridColumn headerText=\"#{object_column.human_name}\" dataField=\"#{belongs_to_assoc.name}\" >";
  		          end
            when 'binary', 'boolean', 'datetime', 'timestamp', 'date', 'float', 'decimal'
              mxml_code = 
              "\t\t\t\t<mx:DataGridColumn headerText=\"#{object_column.human_name}\" dataField=\"#{object_column.name}\" labelFunction=\"format#{object_column.name.camelcase}\"/>";
				    end  
				  
				  if  mxml_code.nil? 
            mxml_code =
            "\t\t\t\t<mx:DataGridColumn headerText=\"#{object_column.human_name}\" dataField=\"#{object_column.name}\"/>";
			end

  				return mxml_code
    end

  
  def get_form_item_reader (object_column)
    
#<%    

			  db_column_type = object_column.type.to_s
        
				case db_column_type  				   
				 
		      when 'integer' 
		        #This may be a simple integer (age in years) or a foreign key to a belongs_to relationship
		        if @belongs_to_class_names.has_key? object_column.name
		            belongs_to_assoc = @belongs_to_class_names[object_column.name]

        				as_code = "\t\t\t\tcurrent#{class_name}.#{belongs_to_assoc.primary_key_name} = cmb#{object_column.name.camelcase}.selectedItemID;"
		            
		        else 
                as_code = "\t\t\t\tcurrent#{class_name}.#{object_column.name} = parseInt(txt#{object_column.name.camelcase}.text);"				  
	          end

				  when 'float', 'decimal'
				    as_code = "\t\t\t\tcurrent#{class_name}.#{object_column.name} = parseFloat(txt#{object_column.name.camelcase}.text);"				  
			            
				  when 'datetime', 'timestamp', 'date'  
            as_code = "\t\t\t\tcurrent#{class_name}.#{object_column.name} = df#{object_column.name.camelcase}.selectedDate;"				   
         
          when 'binary', 'boolean'
            as_code = "\t\t\t\tcurrent#{class_name}.#{object_column.name} = cb#{object_column.name.camelcase}.selected;"				   
                            			
				  else  #'char', 'string', 'text', 'time'   
            as_code = "\t\t\t\tcurrent#{class_name}.#{object_column.name} = txt#{object_column.name.camelcase}.text;"				  
				end
				
				return as_code
  end
  
    # Based on the Active Record column type, build MXML code to format data
    def get_label_function (object_column)

  			  db_column_type = object_column.type.to_s
          indent = "\t\t\t\t"
  				case db_column_type  				   
  		            				  
            when 'float', 'decimal'
                as_code =
                indent +"private function format#{object_column.name.camelcase}( item : Object, column : DataGridColumn ) : String {\n"+
                indent +"\treturn GenuflexDataFormatter.formatNumber(item.#{object_column.name}); \n"+
                indent +"}"			

            when 'date'
                as_code =
                indent +"private function format#{object_column.name.camelcase}( item : Object, column : DataGridColumn ) : String {\n"+
                indent +"\treturn GenuflexDataFormatter.formatDate(item.#{object_column.name}); \n"+
                indent +"}"			
      			
    				  when 'timestamp', 'datetime'  
                as_code =
                indent +"private function format#{object_column.name.camelcase}( item : Object, column : DataGridColumn ) : String {	\n"+			
                indent +"\treturn GenuflexDataFormatter.formatDateTime(item.#{object_column.name}); \n"+
                indent +"}"			

              when 'binary', 'boolean'
                as_code = 
                indent +"private function format#{object_column.name.camelcase}( item : Object, column : DataGridColumn ) : String {\n"+
                indent +"\treturn item.#{object_column.name} ? 'Yes' : 'No'; \n"+
                indent +"}"			

  				end

  				return as_code
    end

    # Based on the Active Record column type, build MXML code to format data
    def get_label_function_code (class_name)
          object_column = get_scaffold_label_column(class_name)
          
          #It's possible that the developer has entered her own AS code... which means
          # that no column will be found
          if object_column.nil?
            as_code = get_scaffold_label_field(class_name)
          else
            
    			  db_column_type = object_column.type.to_s

    				case db_column_type  				   
      			  when 'integer'
      			        #This may be a simple integer (age in years) or a foreign key to a belongs_to relationship
      			        if @belongs_to_class_names.has_key? object_column.name
      			            belongs_to_assoc = @belongs_to_class_names[object_column.name]

                        as_code = "labelOf('#{belongs_to_assoc.name}')"
      		          end
  		            				  
              when 'float', 'decimal'
                  as_code = "GenuflexDataFormatter.formatNumber(#{object_column.name})"

              when 'date'
                  as_code =  "GenuflexDataFormatter.formatDate(#{object_column.name})"
      			
      				when 'timestamp', 'datetime'  
                  as_code = "GenuflexDataFormatter.formatDateTime(#{object_column.name})"

              when 'binary', 'boolean'
                  as_code = "#{object_column.name} ? 'Yes' : 'No'"
      				when 'text', 'char', 'string'
                  as_code = "#{object_column.name}"

    				end
    				
    				# When all else fails, go for toString()
    				if as_code.nil?
    				  as_code = "#{object_column.name}.toString()"
  				  end
    				
          end
          
  				return as_code
    end
    
    def get_belongs_to_name(has_many_assoc)
       # There may not be a corresponding belongs_to
       belongs_to_name = nil
       
       #Get all belongs_to associations for the class referenced in the has_many declaration
       Object.const_get(has_many_assoc.klass().to_s).reflect_on_all_associations(:belongs_to).each do |assoc|
         if assoc.primary_key_name == has_many_assoc.primary_key_name
           belongs_to_name = assoc.name
         end # if match pk
   		 end #each assoc

       #it may be a has_one relationship
       if belongs_to_name.nil?
         #Get all belongs_to associations for the class referenced in the has_many declaration
         Object.const_get(has_many_assoc.klass().to_s).reflect_on_all_associations(:has_one).each do |assoc|
           if assoc.primary_key_name == has_many_assoc.primary_key_name
             belongs_to_name = assoc.name
           end # if match pk
     		 end #each assoc
       end #if belongs_to_name is nil
       
   		return belongs_to_name
     end
     
     def get_has_many_name(belongs_to_assoc)
        # There may not be a corresponding has_many
        has_many_name = nil

        #Get all belongs_to associations for the class referenced in the has_many declaration
        Object.const_get(belongs_to_assoc.klass().to_s).reflect_on_all_associations(:has_many).each do |assoc|
          if assoc.primary_key_name == belongs_to_assoc.primary_key_name
            has_many_name = assoc.name
          end # if match pk
    		 end #each assoc

    		return has_many_name       
     end
     
     def get_habtm_name(habtm_assoc)
       belongs_to_name = nil
       #Get all belongs_to associations for the class referenced in the has_many declaration
       Object.const_get(habtm_assoc.klass().to_s).reflect_on_all_associations(:has_and_belongs_to_many).each do |assoc|

         #Find the association using the same join table
          if assoc.options[:join_table] == habtm_assoc.options[:join_table]  
           belongs_to_name = assoc.name
         end # if match pk
   		end #each assoc

   		return belongs_to_name
     end     
end